Double free

原文借鉴:http://d0m021ng.github.io/2017/02/24/PWN/Linux%E5%A0%86%E6%BC%8F%E6%B4%9E%E4%B9%8BDouble-free/

0x01 Glibc背景知识

1
Linux下堆分配器主要由两个结构管理堆内存,一种是堆块头部形成的隐式链表,另一种是管理空闲堆块的显式链表(Glibc中的bins数据结构)。关于bins的介绍已经有很多,就不赘述了。接下来介绍一下Linux下Double free漏洞原理以及free函数的堆块合并过程。

0x02 Doublefree漏洞原理

1
2
3
4
 free函数在释放堆块时,会通过隐式链表判断相邻前、后堆块是否为空闲堆块;如果堆块为空闲就会进行合并,然后利用Unlink机制将该空闲堆块从Unsorted bin中取下。如果用户精心构造的假堆块被Unlink,很容易导致一次固定地址写,然后转换为任意地址读写,从而控制程序的执行。

free函数原理:由堆块头部形成的隐式链表可知,一个需释放堆块相邻的堆块有两个:前一个块(由当前块头指针加pre_size确定),后一个块(由当前块头指针加size确定)。从而,在合并堆块时会存在两种情况:向后合并、向前合并。当前一个块和当前块合并时,叫做向后合并。当后一个块和当前块合并时,叫做向前合并。
相关代码

0x03 Doublefree漏洞利用原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
以64位应用为例:如果在free一个指针指向的块时,由于堆溢出,将后一个块的块头改成如下格式:
fake_prevsize1 = 被释放块大小;
fake_size1 = 0x20 | 1 (fake_size1 = 0x20)
fake_fd = free@got.plt - 0x18
fake_bk = shellcode address
fake_prevsize2 = 0x20
fake_size2 = 0x10
如下图:
如果chunk0被释放(fake_size1 = 0x21),进行空闲块合并时,1)由于前一个块非空闲,不会向后合并。2)根据chunk2判断后一个块chunk1空闲,向前合并,导致unlink。
如果chunk1被释放(fake_size1 = 0x20),进行空闲块合并时,1)由于前一个块空闲,向后合并,导致unlink。2)根据chunk2判断后一个块chunk1空闲,向前合并,导致unlink。
根据unlink宏知道, 前一个块 FD 指向 free@got.plt - 0x18, 后一个块 BK 指向 shellcode address。然后前一个块 FD 的bk指针即free@got.plt,值为shellcode address, 后一个块 BK 的 fd 指针即shellcode + 0x10,值为 free@got.plt。从而实现了一次固定地址写。

High |----------------|
| fake_size2 |
|----------------|
| fake_prevsize2 |
|----------------| chunk2 pointer
| fake_bk |
|----------------|
| fake_fd |
|----------------| chunk1 malloc returned pointer
| fake_size1 |
|----------------|
| fake_prevsize1 |
|----------------| chunk1 pointer
| ...... |
| padding |
| ...... |
|----------------|
| fake_bk |
|----------------|
| fake_fd |
|----------------| chunk0 malloc returned pointer
| size |
|----------------|
| prev_size |
Low |----------------| chunk0 pointer

但是,由于当前glibc的加固检测机制,会检查显式链表中前一个块的fd与后一个块的bk是否都指向当前需要unlink的块。这样攻击者就无法替换chunk1(或chunk0)的fd与bk。相关代码如下:
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV)

针对这种情况,需要在内存中找到一个指向需要unlink块的指针,就可以绕过。
0%